#ifndef _PARSER_H_
#define _PARSER_H_

#include <iostream>
#include <stdlib.h>
#include <vector>

using namespace std;

#include "lexer.h"
#include "token.h"
#include "error.h"
#include "MismatchedTokenException.h"
#include "PreviousParseFailedException.h"

class Parser {
public:
    const int FAILED = -1;
protected:
    Lexer *input;
    vector<int> markers;
    vector<Token> lookahead;
    int p = 0;
public:
    Parser(Lexer *input) {
        this->input = input;
        sync(1);
    }

    void consume() {
        p++;
        if (p == lookahead.size() && !is_speculating()) {
            p = 0;
            lookahead.clear();
            clear_memo();
        }
        sync(1);
    }

    void sync(int i) {
        int size = lookahead.size() - 1;
        if (p + i - 1 > size) {
            int n = (p + i - 1) - size;
            fill(n);
        }
    }

    void fill(int n) {
        for (int i = 1; i <= n; i++)
            lookahead.push_back(*(input->next_token()));
    }

    Token *LT(int i) {
        sync(i);
        return &lookahead[p + i - 1];
    }

    int LA(int i) {
        return LT(i)->type;
    }

    void match(int x) {
        if (LA(1) == x) {
            // cout << LT(1)->to_string() << endl;
            consume();
        }
        else {
            string s = "expecting ";
            s += input->get_token_name(x);
            s += "; found ";
            s += LT(1)->text;
            throw MismatchedTokenException(s);
        }
    }

    virtual void clear_memo() = 0;

    int mark() {
        markers.push_back(p);
        return p;
    }

    void release() {
        int marker = markers[markers.size() - 1];
        markers.pop_back();
        seek(marker);
    }

    void seek(int index) {
        p = index;
    }

    bool is_speculating() {
        return markers.size() > 0;
    }

    bool already_parsed_rule(map<int, int> &memoization) {
        int memo = memoization[index()];
        if (memo == 0)
            return false;
        cout << "parsed list before at index " << index() << "; skip ahead to token index " \
            << memo << ": " << lookahead[memo].text << endl;
        if (memo == FAILED)
            throw PreviousParseFailedException();
        seek(memo);
        return true;
    }

    void memoize(map<int, int> &memoization, int start_token_index, bool failed) {
        int stop_token_index = failed ? FAILED : index();
        memoization[start_token_index] = stop_token_index;
    }

    int index() {
        return p;
    }
};

#endif
